/*
 * Niiuzu
 * Copyright (C) 2011  Thierry Nowak
 * 
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.niiuzu.server.rpc.impl;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.niiuzu.server.dao.DaoFactory;
import com.niiuzu.server.dao.FeedChannelDao;
import com.niiuzu.server.dao.MailDao;
import com.niiuzu.server.dao.ParameterDao;
import com.niiuzu.server.dao.UserDao;
import com.niiuzu.server.dao.model.MailRequest;
import com.niiuzu.server.i18n.ServerI18nMessages;
import com.niiuzu.server.i18n.ServerI18nMessages.Bundle;
import com.niiuzu.server.model.ParameterName;
import com.niiuzu.server.util.PasswordUtil;
import com.niiuzu.server.util.SymCryptUtil;
import com.niiuzu.server.util.exception.IllegalCryptedTextValueException;
import com.niiuzu.shared.exception.EmailAddressAlreadyExistsException;
import com.niiuzu.shared.exception.ServiceRpcException;
import com.niiuzu.shared.exception.UnknownEmailAddressException;
import com.niiuzu.shared.exception.WrongUserReferenceException;
import com.niiuzu.shared.model.ClientContext;
import com.niiuzu.shared.model.FeedChannel;
import com.niiuzu.shared.model.FeedChannelGroup;
import com.niiuzu.shared.model.User;
import com.niiuzu.shared.model.UserLogInResult;
import com.niiuzu.shared.model.UserSettings;
import com.niiuzu.shared.rpc.UserService;

/**
 * Implémentation du service dédié aux utilisateurs.
 */
public class UserServiceImpl extends RemoteServiceServlet implements
		UserService {

	private static final long serialVersionUID = 3433845667124813710L;

	private static final Logger LOGGER = Logger.getLogger(UserServiceImpl.class
			.getName());

	private static final int GENERATED_PASSWORD_LENGTH = 8;

	@Override
	public UserLogInResult logUserInFromCredentials(String mail,
			String password, boolean rememberUser, ClientContext context)
			throws ServiceRpcException {
		LOGGER
				.info("Tentative de connexion avec les identifiants pour "
						+ mail);

		UserLogInResult result = null;

		try {

			UserDao userDao = getUserDao();

			User user = userDao.getUserWithCredentials(mail, password);

			// Un utilisateur correspond aux identifiants
			if (user != null) {

				// Calcule la valeur cryptée de la référence à l'utilisateur
				String cryptedUserReference = null;
				if (rememberUser) {
					ParameterDao parameterDao = getParameterDao();

					String symCryptKey = parameterDao
							.getParameterValue(ParameterName.SYMCRYPT_KEY_REMEMBER_USER);
					cryptedUserReference = SymCryptUtil.encrypt(symCryptKey,
							user.getMail());
				}

				result = new UserLogInResult();
				result.setUser(user);
				result.setCryptedUserReference(cryptedUserReference);
			}

		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, "Erreur lors de la connexion pour" + mail,
					e);
			throw new ServiceRpcException(e);
		} finally {
			LOGGER.info("Fin de la connexion avec les identifiants pour "
					+ mail);
		}

		return result;
	}

	@Override
	public UserLogInResult logUserInAtStartUp(String cryptedUserReference,
			ClientContext context) throws ServiceRpcException,
			WrongUserReferenceException {

		LOGGER.info("Tentative de connexion au lancement de l'application");

		UserLogInResult result = null;

		try {

			ParameterDao parameterDao = getParameterDao();
			String symCryptKey = parameterDao
					.getParameterValue(ParameterName.SYMCRYPT_KEY_REMEMBER_USER);

			// Décryptage de la référence de l'utilisateur
			String userEmail = SymCryptUtil.decrypt(symCryptKey,
					cryptedUserReference);

			// Récupération de l'utilisateur correspondant
			UserDao userDao = getUserDao();
			User user = userDao.getUserByEmail(userEmail);

			if (user == null) {
				throw new IllegalArgumentException();
			} else {
				result = new UserLogInResult();
				result.setUser(user);
			}

		} catch (IllegalCryptedTextValueException e) {
			StringBuilder message = new StringBuilder();
			message.append("Décryptage de la référence de l'utilisateur");
			message.append(" impossible : ");
			message.append(cryptedUserReference);
			LOGGER.log(Level.WARNING, message.toString(), e);
			throw new WrongUserReferenceException(message.toString(), e);
		} catch (IllegalArgumentException e) {
			StringBuilder message = new StringBuilder();
			message.append("Aucune correspondance avec la valeur décryptée :");
			message.append(" cryptedUserReference=");
			message.append(cryptedUserReference);
			LOGGER.log(Level.WARNING, message.toString());
			throw new WrongUserReferenceException(message.toString(), e);
		} catch (Exception e) {
			LOGGER
					.log(
							Level.SEVERE,
							"Erreur lors de la connexion au lancement de l'application",
							e);
			throw new ServiceRpcException(e);
		} finally {
			LOGGER.info("Fin de la connexion au lancement de l'application");
		}

		return result;
	}

	@Override
	public UserSettings retrieveUserSettings(User user, ClientContext context)
			throws ServiceRpcException {

		LOGGER.info("Récupération des préférences de l'utilisateur");

		UserSettings settings = null;

		try {

			FeedChannelDao feedChannelDao = getFeedChannelDao();

			List<FeedChannelGroup> groupList = feedChannelDao
					.getChannelGroups(user);
			FeedChannelGroup[] groups = new FeedChannelGroup[groupList.size()];
			groups = groupList.toArray(groups);

			List<FeedChannel> channelList = feedChannelDao.getChannels(user);
			FeedChannel[] channels = new FeedChannel[channelList.size()];
			channels = channelList.toArray(channels);

			settings = new UserSettings();

			settings.setChannelGroups(groups);
			settings.setChannels(channels);

		} catch (Exception e) {
			LOGGER.log(Level.SEVERE,
					"Erreur lors de la récupération des préférences", e);
			throw new ServiceRpcException(e);
		} finally {
			LOGGER.info("Fin de la récupération des préférences");
		}

		return settings;
	}

	@Override
	public UserLogInResult createUserAccount(String mail, String password,
			ClientContext context) throws ServiceRpcException,
			EmailAddressAlreadyExistsException {

		LOGGER.info("Création d'un compte utilisateur");

		UserLogInResult result = null;

		try {

			UserDao userDao = getUserDao();

			if (userDao.isEmailAddressAlreadyUsed(mail)) {
				throw new IllegalStateException();
			}

			User user = userDao.createUserAccount(mail, password);

			result = new UserLogInResult();
			result.setUser(user);

		} catch (IllegalStateException e) {
			final String key = "userUnavailableMailAddress";
			final String error = getErrorMessages(context).getMessage(key);
			throw new EmailAddressAlreadyExistsException(error, e);
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE,
					"Erreur lors de la création du compte utilisateur", e);
			throw new ServiceRpcException(e);
		} finally {
			LOGGER.info("Fin de la création du compte utilisateur");
		}

		return result;
	}

	@Override
	public void solveLostPasswordProblem(String email, ClientContext context)
			throws ServiceRpcException, UnknownEmailAddressException {

		LOGGER.info("Début résolution mot de passe perdu pour " + email);

		try {

			UserDao userDao = getUserDao();

			User user = userDao.getUserByEmail(email);
			if (user == null) {
				throw new IllegalStateException();
			}

			String generatedPassword = PasswordUtil
					.generatePassword(GENERATED_PASSWORD_LENGTH);

			userDao.updateUserPassword(user, generatedPassword);

			ServerI18nMessages i18nMessages = new ServerI18nMessages(
					Bundle.MailMessages, context);

			String senderAddress = i18nMessages.getMessage("appEmailAddress");
			String mailTitle = i18nMessages.getMessage("lostPasswordTitle");
			String mailTextBody = i18nMessages.getMessage(
					"lostPasswordTextBody", user.getMail(), generatedPassword);

			// Construit et envoie un email
			MailDao mailDao = getMailDao();
			MailRequest request = new MailRequest();
			request.setSenderAddress(senderAddress);
			request.setReceiverAddress(email);
			request.setTitle(mailTitle);
			request.setContent(mailTextBody);

			mailDao.sendMail(request);

		} catch (IllegalStateException e) {
			final String key = "unknownMailAddress";
			final String error = getErrorMessages(context).getMessage(key);
			throw new UnknownEmailAddressException(error, e);
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE,
					"Erreur lors de la résolution mot de passe perdu pour "
							+ email, e);
			throw new ServiceRpcException(e);
		} finally {
			LOGGER
					.info("Fin de la résolution mot de passe perdu pour "
							+ email);
		}
	}

	/**
	 * Récupère le bundle contenant les messages d'erreurs.
	 */
	private ServerI18nMessages getErrorMessages(ClientContext context) {
		return new ServerI18nMessages(Bundle.ErrorMessages, context);
	}

	/**
	 * Retourne une instance de {@link UserDao}.
	 */
	private UserDao getUserDao() {
		if (mockedUserDao != null) {
			return mockedUserDao;
		}
		return DaoFactory.getUserDao();
	}

	/**
	 * Retourne une instance de {@link FeedChannelDao}.
	 */
	private FeedChannelDao getFeedChannelDao() {
		if (mockedFeedChannelDao != null) {
			return mockedFeedChannelDao;
		}
		return DaoFactory.getFeedChannelDao();
	}

	/**
	 * Retourne une instance de {@link ParameterDao}.
	 */
	private ParameterDao getParameterDao() {
		if (mockedParameterDao != null) {
			return mockedParameterDao;
		}
		return DaoFactory.getParameterDao();
	}

	/**
	 * Retourne une instance de {@link MailDao}.
	 */
	private MailDao getMailDao() {
		if (mockedMailDao != null) {
			return mockedMailDao;
		}
		return DaoFactory.getMailDao();
	}

	/* ********************************************************************** */
	/* ********************************************************************** */
	/* ********************************************************************** */

	private UserDao mockedUserDao;
	private FeedChannelDao mockedFeedChannelDao;
	private ParameterDao mockedParameterDao;
	private MailDao mockedMailDao;

	/**
	 * JUnit - Définit un mock pour {@link FeedChannelDao}.
	 */
	void setMockedFeedChannelDao(FeedChannelDao mockedFeedChannelDao) {
		this.mockedFeedChannelDao = mockedFeedChannelDao;
	}

	/**
	 * JUnit - Définit un mock pour {@link UserDao}.
	 */
	void setMockedUserDao(UserDao mockedUserDao) {
		this.mockedUserDao = mockedUserDao;
	}

	/**
	 * JUnit - Définit un mock pour {@link ParameterDao}.
	 */
	void setMockedParameterDao(ParameterDao mockedParameterDao) {
		this.mockedParameterDao = mockedParameterDao;
	}

	/**
	 * JUnit - Définit un mock pour {@link MailDao}.
	 */
	public void setMockedMailDao(MailDao mockedMailDao) {
		this.mockedMailDao = mockedMailDao;
	}
}
